Jx.declare('Jx.event.Listener', {

	$category: 'event.listener.default',

	$accessors: [ 'name' ],

	$helpers: {

		'Fn': 'Jx.lib.Function',

		'Mgr': 'Jx.event.Manager'

	},

	$actions: [

		'addHandler',

		'removeHandler',

		'clearHandlers',

		'exec'

	],

	$$precallback: function(properties, SuperClass, struct){

		var path = struct.categoryPath,

			superOptions = SuperClass.prototype.options,

			ns = 'event.listener.',

			J = Jx,

			type, options;

		// fix category
		if (path && !properties.hasOwnProperty('type')){

			properties.type = path.substring(0, ns.length) == ns ?

				path.substring(ns.length, path.length)

				:

				path;

		}

		// allowed options
		options = properties.options = J.isArray(properties.options) ?

			properties.options

			:

			[];


		if (J.isArray(superOptions)){

			options.unshift.apply(options, superOptions);

		}

	},

	__help_GetHandlerLastIndexOf: function(fn, scope){

		for(var obj, handlers = this.handlers, l = handlers.length; l--;){

			obj = handlers[l].__event_handler_struct;

			if (obj.fn === fn && obj.scope === scope){

				return l;

			}

		}

		return -1;

	},

	options: [ 'scope', 'buffer', 'defer' ],

	handlers: undefined,

	dispatcher: undefined,

	constructor: function(){

		this.handlers = [];

		this.$super(arguments);

	},

	onConstruct: function(dispatcher, name){

		this.dispatcher = dispatcher;

		this.setName(name || this.type);

		this.$super();

	},

	onDestroy: function(){

		// destroy handlers
		this.clearHandlers();

		this.$super();

	},

	allowAddHandler: function(o, fn, scope, options){

		var J = Jx,

			valid = o.valid = J.isFunction(fn),

			params = o.params;

		if (valid){

			params[1] = fn;

			params[2] = scope || null;

			params[3] = J.isObject(options) ? options : {};

		}

	},

	onAddHandler: function(o, callback, scope, options){

		var handlerProperties = {

				cleanups: [],

				cleanupHandlers: {},

				handler: callback,

				fn: callback,

				scope: scope

			},

			handler;

		//-- basic scope processor
		options.scope = scope;

		this.onProcessHandler( handlerProperties, callback, scope, options );

		(handler = handlerProperties.handler).__event_handler_struct = handlerProperties;

		this.handlers.unshift( handler );

	},

	onProcessHandler: function( handlerProperties, callback, scope, options ){

		var optionNames = this.options,

			J = Jx;

		// process options
		for(var name, capitalized, value, old, method, c=-1, l = optionNames.length; l--;){

			if (options.hasOwnProperty(name = optionNames[++c])){

				value = options[name];

				capitalized = name.charAt(0).toUpperCase() + name.substring(1, name.length);

				if (

					(method = 'onSynthesizeFromOption_' + capitalized) in this &&

					J.isFunction(this[method])

				){

					old = handlerProperties.handler;

					this[method](handlerProperties, value);

					//-- set destructor
					if (handlerProperties.handler !== old){

						method = 'onCleanupSynthesizedHandler_' + capitalized;

						if (

							!(method in this) ||

							!J.isFunction( this[method] )

						){

							this[method] = function(){}

						}

						handlerProperties.cleanups.push(method);

						handlerProperties.cleanupHandlers[method] = handlerProperties.handler;

					}

				}

			}

		}

	},

	onCallHandler: function( handler, eventObject, callback, scope){

		var returned = callback.call(scope, eventObject);

		if (returned === false){

			eventObject.setReturnValue( returned );

		}

	},

	allowRemoveHandler: function(o, fn, scope){

		var index = -1;

		// use it as index
		if (Jx.isNumber(fn) && fn > -1 && fn < this.handlers.length){

			index = fn;

		} else {

			index = this.__help_GetHandlerLastIndexOf(fn, scope);

		}

		o.valid = (

			o.params[1] = o.index = index

		) != -1;

	},

	onRemoveHandler: function(o, index){

		o.execAfter = true;

		o.handler = this.handlers.splice( index, 1 )[0];

	},

	onAfterRemoveHandler: function(o){

		var handler = o.handler,

			obj = handler.__event_handler_struct,

			handlers = obj.cleanupHandlers,

			J = Jx;

		// execute cleanup methods
		for(var name, h, names = obj.cleanups, l = names.length; l--;){

			this[name = names[l]]( obj, h = handlers[name] );

			J.clearObject( h );

			delete handlers[name];

		}

		// destroy
		J.clearObject( obj );

		J.clearObject( handler );

	},

	allowClearHandlers: function(o){

		var handlers = this.handlers;

		o.valid = handlers.length;

		o.params[1] = handlers;

	},

	onClearHandlers: function(o, handlers){

		for(var l = handlers.length; l--;){

			this.removeHandler(l);

		}

	},

	allowExec: function(o, eventObject){

		o.valid = Jx.instanceOf(

				o.params[1] = o.eventObject = eventObject,

				this.__Mgr.__ref_DefaultEventOperationClass

			) &&

			!eventObject.destroyed;

	},

	onExec: function(o, eventObject){

		//-- get next bubble target
		var dispatcher = this.dispatcher,

			handlers = this.handlers.slice(0);

		//-- update current target
		eventObject.setCurrentTarget( dispatcher.getObservable() );

		for(var l = handlers.length; l--;){

			this.onExecHandler(handlers[l], o.eventObject);

			// in case this listener is destroyed along the way
			if (this.destroyed || eventObject.destroyed){

				break;

			}

		}

		handlers.splice(0, handlers.length);

		handlers = null;

	},

	onExecHandler: function(handler, obj){

		handler(obj);

	},

	hasHandlers: function(fn, scope){

		return this.__help_GetHandlerLastIndexOf(fn, scope) != -1;

	},


/**
 *	Handler Synthesizer
 */
	onSynthesizeFromOption_Scope: function(o, v){

		function Synthesized_Context( eventObject ){

			var fn = arguments.callee,

				listener = fn.Mgr,

				o = fn.handlerProperties;

			listener.onCallHandler( o.handler, eventObject, o.fn, o.scope );

		}

		Synthesized_Context.handlerProperties = o;

		Synthesized_Context.Mgr = this;

		o.handler = Synthesized_Context;

	},

	onSynthesizeFromOption_Defer: function(o, v){

		if (Jx.isNumber(v) && v > 0){

			function Synthesized_Defer_Handler( eventObject, deferObj ){

				var _ = arguments.callee,

					pid = deferObj.pid,

					list = _.defered,

					index = list.lastIndexOf(pid);

				list.splice(index, 1);

				list = eventObject.deferred;

				index = list.lastIndexOf(pid);

				list.splice(index);

				// at this time, maybe the listener is already deleted
				if (!_.listener.destroyed && !eventObject.destroyed){

					_.handler( eventObject );

				}

			}

			function Synthesized_Defer( eventObject ){

				var _ = arguments.callee,

					deferObject = { pid: 0 },

					list = _.defered,

					opList = eventObject.deferred;

				list[list.length] = opList[opList.length] = deferObject.pid = _.F.defer(_.delay, _.callback, null, [ eventObject, deferObject ])

			}

			Synthesized_Defer_Handler.listener = this;

			Synthesized_Defer_Handler.handler = o.handler;

			Synthesized_Defer_Handler.defered = Synthesized_Defer.defered = o.defered = [];

			Synthesized_Defer.delay = v;

			Synthesized_Defer.F = this.__Fn;

			Synthesized_Defer.callback = Synthesized_Defer_Handler;

			o.handler = Synthesized_Defer;

		}

	},

	onSynthesizeFromOption_Buffer: function(o, value){

		if (Jx.isNumber(value)){

			o.handler = this.__Fn.buffer(value, o.handler);

		}

	},

/**
 *	Synthesized Handler Cleanup
 */
	onCleanupSynthesizedHandler_Defer: function(o, handler){

		var Fn = this.__Fn;

		// clear defered
		for(var pids = o.defered, l = pids.length; l--;){

			Fn.stop(pids[l]);

			pids.splice(l, 1);

		}

	}


});
